package org.putrpctorest.service.impl.spring;

import java.util.HashSet;
import java.util.Set;

import org.apache.cxf.helpers.DOMUtils;
import org.putrpctorest.resource.CollectionType;
import org.putrpctorest.resource.ParameterType;
import org.putrpctorest.resource.Verb;
import org.putrpctorest.resource.impl.MethodImpl;
import org.putrpctorest.resource.impl.ParameterImpl;
import org.putrpctorest.resource.impl.ResourceImpl;
import org.putrpctorest.resource.impl.ResourcesImpl;
import org.w3c.dom.Element;

import sun.reflect.ReflectionFactory.GetReflectionFactoryAction;

public class ResourcesParser {

	private final static String REST_SERVICE_NS = "http://www.example.org/rest-services";
	private final static Object[][] PRIMITIVE_TYPE_NAMES = {
			{ "String", String.class }, { "char", char.class },
			{ "byte", byte.class }, { "short", short.class },
			{ "int", int.class }, { "long", long.class },
			{ "float", float.class }, { "double", double.class },
			{ "boolean", boolean.class }, { "Character", Character.class },
			{ "Byte", Byte.class }, { "Short", Short.class },
			{ "Integer", Integer.class }, { "Long", Long.class },
			{ "Float", Float.class }, { "Double", Double.class },
			{ "Boolean", Boolean.class },
			{ "void", void.class }		};

	public static ResourcesImpl parse(Element rootElement) {

		return parseResourcesElement(rootElement);
	}

	private static ResourcesImpl parseResourcesElement(Element rootElement) {
		checkElementType(rootElement, "rest-resources");

        ResourcesImpl resources = new ResourcesImpl();
		
		
        resources.setId(rootElement.getAttribute("id"));
        
		
        resources.setBasePath(rootElement.getAttribute("base-path"));
		
        resources.addProduces(parseMediaTypesFromParentElement(rootElement,
		"produces"));

        resources.addConsumes(parseMediaTypesFromParentElement(rootElement,
		"consumes"));
		
		
		Element resourceElement = DOMUtils.getFirstChildWithName(rootElement,
				REST_SERVICE_NS, "resource");
		while (resourceElement != null) {
		    resources.addResource(parseResource(resourceElement));
			resourceElement = DOMUtils.getNextElement(resourceElement);
		}
		return resources;

	}

	private static ResourceImpl parseResource(Element resourceElement) {
		checkElementType(resourceElement, "resource");
		ResourceImpl resource = new ResourceImpl();
		resource.setId(resourceElement.getAttribute("id"));
		resource.setPath(resourceElement.getAttribute("path"));

		resource.setProduces(parseMediaTypesFromParentElement(resourceElement,
				"produces"));

		resource.setConsumes(parseMediaTypesFromParentElement(resourceElement,
				"consumes"));

		Element methodElement = DOMUtils.getFirstChildWithName(resourceElement,
				REST_SERVICE_NS, "method");

		while (methodElement != null) {
			resource.addMethod(parseMethod(methodElement));
			methodElement = DOMUtils.getNextElement(methodElement);
		}
		return resource;
	}

	private static MethodImpl parseMethod(Element methodElement) {
		checkElementType(methodElement, "method");

		MethodImpl method = new MethodImpl();

		method.setVerb(Verb.valueOf(methodElement.getAttribute("verb")));

		method.setProduces(parseMediaTypesFromParentElement(methodElement,
				"produces"));

		method.setConsumes(parseMediaTypesFromParentElement(methodElement,
				"consumes"));

		Element parameterElement = DOMUtils.getFirstChildWithName(
				methodElement, REST_SERVICE_NS, "parameter");

		while (parameterElement != null && parameterElement.getNodeName().equals("parameter")) {
			method.addParameter(parserParameter(parameterElement));
			parameterElement = DOMUtils.getNextElement(parameterElement);

		}
		
        Element returnEntityElement = DOMUtils.getFirstChildWithName(
                methodElement, REST_SERVICE_NS, "return-entity");

		method.setReturnEntity(parserReturnEntity(returnEntityElement));

		return method;
	}

	private static ParameterImpl parserParameter(Element parameterElement) {
		checkElementType(parameterElement, "parameter");

		ParameterImpl parameter = new ParameterImpl();
		parameter.setName(parameterElement.getAttribute("name"));
		parameter.setType(ParameterType.valueOf(parameterElement
				.getAttribute("type")));
		parameter.setJavaType(getJavaTypeFromName(parameterElement
				.getAttribute("java-type")));
		String collectionType = parameterElement
				.getAttribute("collection-type");
		if (collectionType != null && (!"".equals(collectionType.trim()))) {
			parameter.setCollectionType(CollectionType.valueOf(collectionType));
		}
		String defaultValue = parameterElement.getAttribute("default-value");
		if (defaultValue != null && (!"".equals(defaultValue.trim()))) {
			parameter.setDefaultValue(defaultValue);
		}
		String encoded = parameterElement.getAttribute("encoded");
		if (encoded != null && (!"".equals(encoded.trim()))) {
			parameter.setEncoded(Boolean.valueOf(encoded));
		}

		return parameter;

	}
	
	private static Class<?> parserReturnEntity(Element parameterElement) {
	    checkElementType(parameterElement, "return-entity");
	    return getJavaTypeFromName( parameterElement.getAttribute("type"));
	}

	protected static Class<?> getJavaTypeFromName(String typeName) {

		Class<?> javaType = getPrimitiveJavaType(typeName);

		if (javaType != null) {
			return javaType;
		}

		javaType = getOtherJavaType(typeName);

		if (javaType != null) {
			return javaType;
		}
		throw new RuntimeException("Could not find type " + typeName);
	}

	private static Class<?> getOtherJavaType(String typeName) {
		try {
			return Class.forName(typeName);
		} catch (ClassNotFoundException e) {
			return null;
		}
	}

	private static Class<?> getPrimitiveJavaType(String typeName) {
		for (Object[] typeDefinition : PRIMITIVE_TYPE_NAMES) {
			if (typeName.equals(typeDefinition[0])) {
				return (Class<?>) typeDefinition[1];
			}
		}
		return null;
	}

	private static Set<String> parseMediaTypesFromParentElement(
			Element rootElement, String parentElementName) {
		Set<String> mediaTypes = new HashSet<String>();
		Element parentElement = DOMUtils.getFirstChildWithName(rootElement,
				REST_SERVICE_NS, parentElementName);

		if (parentElement == null) {
			return mediaTypes;
		} else {
			Element mediaTypeElement = DOMUtils.getFirstChildWithName(
					parentElement, REST_SERVICE_NS, "media-type");
			while (mediaTypeElement != null) {
				mediaTypes.add(mediaTypeElement.getTextContent());
				mediaTypeElement = DOMUtils.getNextElement(mediaTypeElement);
			}
			return mediaTypes;
		}
	}

	private static void checkElementType(Element element, String type) {
		if ((!element.getNodeName().equals(type))
				&& (!element.getNodeName().endsWith(":" + type))) {
			throw new RuntimeException("Element must be " + type
					+ " but it is " + element.getNodeName());
		}
	}

}
